--[[
Title: StyleSheet
Author(s): wxa
Date: 2020/6/30
Desc: 样式表
use the lib:
-------------------------------------------------------
]]

local Parser = import("./Parser.lua");
local StyleSheet = commonlib.inherit(commonlib.ToolBase);

local function StringTrim(str, ch)
    ch = ch or "%s";
    str = string.gsub(str, "^" .. ch .. "*", "");
    str = string.gsub(str, ch .. "*$", "");
    return str;
end

-- 获取尾部选择器
local function GetTailSelector(comboSelector)
    if (not comboSelector) then return end

    comboSelector = string.gsub(comboSelector, "%s*$", "");
    
    -- 伪类选择器
    selector = string.match(comboSelector, ":nth%-child%((%d-)%)$");
    if (selector) then return selector, "nth-child", string.gsub(comboSelector, ":nth%-child%(%d-%)$", "") end 
    
    -- 属性选择器
    selector = string.match(comboSelector, "%[([%w%d=]-)%]$");
    if (selector) then return selector, "[", string.gsub(comboSelector, "%[[%w%d=]-%]$", "") end

    -- 后代选择器 div p
    local selector = string.match(comboSelector, "%s* ([^%s%+%~%>]-)$");
    if (selector) then return selector, " ", string.gsub(comboSelector, "%s* [^%s%+%~%>]-$", "") end

    -- 子选择器 div>p
    selector = string.match(comboSelector, "%s*%>%s*([^%s%+%~%>]-)$");
    if (selector) then return selector, ">", string.gsub(comboSelector, "%s*%>%s*[^%s%+%~%>]-$", "") end

    -- 后续兄弟选择器 div~p
    selector = string.match(comboSelector, "%s*%~%s*([^%s%+%~%>]-)$");
    if (selector) then return selector, "~", string.gsub(comboSelector, "%s*%~%s*[^%s%+%~%>]-$", "") end

    -- 相邻兄弟选择器 div+p
    selector = string.match(comboSelector, "%s*%+%s*([^%s%+%~%>]-)$");
    if (selector) then return selector, "+", string.gsub(comboSelector, "%s*%+%s*[^%s%+%~%>]-$", "") end
   
    return nil;
end

-- 是否是有效的元素选择器
local function IsValidElementSelector(selector, element, selectorType)
    local elementSelector = element:GetStyle():GetSelector();
    if (not selector) then return false end
    
    if (selectorType == "nth-child") then
        selector = tonumber(selector);
        if (selector == element:GetIndexInParent()) then return true end 
        return false;
    elseif (selectorType == "[") then
        local attrKey, attrVal = string.match(selector, "(%w+)=(%w+)");
        local realAttrValue = element:GetAttrStringValue(attrKey);
        if (realAttrValue ~= attrVal) then return false end
        return true;
    end

    if (not elementSelector[selector]) then return false end
    if ((string.match(selector, ":hover%s*$")) and not element:IsHover()) then return false end
    return true;
end

-- 是否是祖先元素的选择器
local function IsAncestorElementSelector(element, selector)
    local parentElement = element:GetParent();
    if (not parentElement) then return false end
    if (IsValidElementSelector(selector, parentElement)) then return true, parentElement end
    return IsAncestorElementSelector(parentElement, selector);
end

-- 是否是元素的选择器
local function IsElementSelector(comboSelector, element)
    local selector, selectorType, newComboSelector = GetTailSelector(comboSelector, element);
    if (not IsValidElementSelector(selector, element, selectorType)) then return false end
    local newSelector, newSelectorType = GetTailSelector(newComboSelector);
    newSelector = StringTrim(newSelector or newComboSelector);
    -- 后代选择器 div p
    if (selectorType == " ") then
        local isAncestorElementSelector, ancestorElement = IsAncestorElementSelector(element, newSelector);
        if (not isAncestorElementSelector) then return false end
        if (not newSelectorType) then return true end
        return IsElementSelector(newComboSelector, ancestorElement);
    end

    -- 子选择器 div>p
    if (selectorType == ">") then
        local parentElement = element:GetParent();
        if (not parentElement) then return false end
        if (not IsValidElementSelector(newSelector, parentElement)) then return false end
        if (not newSelectorType) then return true end
        return IsElementSelector(newComboSelector, parentElement);
    end

    -- 后续兄弟选择器 div~p
    if (selectorType == "~") then
        local prevSiblingElement = element:GetPrevSiblingElement();
        while (prevSiblingElement) do
            if (IsValidElementSelector(newSelector, prevSiblingElement)) then break end
            prevSiblingElement = prevSiblingElement:GetPrevSiblingElement();
        end
        if (not prevSiblingElement) then return false end
        if (not newSelectorType) then return true end
        return IsElementSelector(newComboSelector, prevSiblingElement);
    end

    -- 相邻兄弟选择器 div+p
    if (selectorType == "+") then
        -- StyleSheetDebug.If(element:GetAttrStringValue("id") == "debug", "--------------------------------------1");
        local prevSiblingElement = element:GetPrevSiblingElement();
        if (not prevSiblingElement) then return false end
        -- StyleSheetDebug.If(element:GetAttrStringValue("id") == "debug", "--------------------------------------2", prevSiblingElementSelector, newSelector, newComboSelector, selector, comboSelector);
        if (not IsValidElementSelector(newSelector, prevSiblingElement)) then return false end
        -- StyleSheetDebug.If(element:GetAttrStringValue("id") == "debug", "--------------------------------------3");
        if (not newSelectorType) then return true end
        -- StyleSheetDebug.If(element:GetAttrStringValue("id") == "debug", "--------------------------------------4");
        return IsElementSelector(newComboSelector, prevSiblingElement);
    end

    if (newSelector == "" or IsValidElementSelector(newSelector, element)) then return true end
     
    return IsElementSelector(newSelector, element);
end

function StyleSheet:ctor()
    self.__selector_style__ = {};
    self.__animation_style__ = {};
end

function StyleSheet:Clone()
    local clone = StyleSheet:new():Init();

    clone.__selector_style__ = setmetatable({}, {__index = self.__selector_style__});
    clone.__animation_style__ = setmetatable({}, {__index = self.__animation_style__});

    return clone;
end

function StyleSheet:Init()
    return self;
end

-- 压栈
-- function StyleSheet:Push()
--     self.__selector_style__ = setmetatable({}, {__index = self.__selector_style__});
--     self.__animation_style__ = setmetatable({}, {__index = self.__animation_style__});
-- end

-- function StyleSheet:Pop()
--     self.__selector_style__ = getmetatable(self.__selector_style__).__index;
--     self.__animation_style__ = getmetatable(self.__animation_style__).__index;
-- end

function StyleSheet:Clear()
    self.__selector_style__ = {};
    self.__animation_style__ = {};
end

function StyleSheet:SetSelectorStyle(selector, style)
    if (rawget(self.__selector_style__, selector)) then
        commonlib.partialcopy(self.__selector_style__[selector], style);
    else 
        local inherit_style = self.__selector_style__[selector];
        self.__selector_style__[selector] = setmetatable({}, {__index = inherit_style});
        commonlib.partialcopy(self.__selector_style__[selector], style);
    end
end

function StyleSheet:LoadFile(filename)
    local text = commonlib.GetFileText(filename);
    self:LoadString(text or "");
end

function StyleSheet:LoadString(code)
    code = string.gsub(code,"/%*.-%*/","");
    local csscode, lastCssCodePos, codelen = "", 1, string.len(code);
    -- 查找动画关键字
    local startPos, endPos = string.find(code, "@keyframes", 1, true);
    if (not startPos) then csscode = code end
    while(startPos) do
        local animationName = string.match(string.sub(code, endPos + 1), "([^{}]+)");
        local index, count = string.find(code, "{", endPos + string.len(animationName), true), 1;
        local sp, ep = nil, nil;
        while (index and index < codelen) do
            index = index + 1;
            if (sp == nil) then sp = index end 
            local ch = string.sub(code, index, index);
            if (ch == "{") then count = count + 1 end
            if (ch == "}") then count = count - 1 end
            if (count == 0) then 
                ep = index - 1 
                break;
            end
        end
        local animationCss = string.sub(code, sp, ep);
        animationName = string.match(animationName, "^%s*(.-)%s*$");
        local animation = self.__animation_style__[animationName] or {};
        self.__animation_style__[animationName] = animation;

        -- print(animationName);
        -- print(animationCss);

        for selector_str, declaration_str in string.gmatch(animationCss, "([^{}]+){([^{}]+)}") do
            local style = Parser.ParseString(declaration_str);
            for selector in string.gmatch(selector_str, "([^,]+),?") do
                selector = string.match(selector, "^%s*(.-)%s*$");
                if (selector == "from") then selector = 0
                elseif (selector == "to") then selector = 100
                else 
                    selector = string.match(selector, "(%d+)");
                    selector = selector and tonumber(selector) or 0;
                end
                style.percentage = selector;
                table.insert(animation, style);
            end
        end
        table.sort(animation, function(item1, item2) return item1.percentage < item2.percentage end);

        csscode = csscode .. string.sub(lastCssCodePos, startPos - 1);
        startPos, endPos = string.find(code, "@keyframes", index + 1, true);
        if (not startPos) then 
            csscode = csscode .. string.sub(code, index + 1);
        else 
            lastCssCodePos = index + 1;
        end
    end

    for selector_str, declaration_str in string.gmatch(csscode, "([^{}]+){([^{}]+)}") do
        local style = Parser.ParseString(declaration_str);
        for selector in string.gmatch(selector_str, "([^,]+),?") do
            selector = string.match(selector, "^%s*(.-)%s*$");
            self:SetSelectorStyle(selector, style);
        end
    end

    return self;
end

-- 设置基础样式表
function StyleSheet:SetInheritStyleSheet(sheet)
    self.__inherit_stylesheet__ = sheet;
end

-- 生效选择器样式
function StyleSheet:ApplySelectorStyle(selector, style)
    -- 选择器默认样式
    local selector_style = self.__selector_style__[selector];
    if (selector_style) then commonlib.partialcopy(style:GetSelectorStyle(), selector_style) end

    -- 选择器激活样式
    local active_selector = selector .. ":active";
    selector_style = self.__selector_style__[active_selector];
    if (selector_style) then commonlib.partialcopy(style:GetActiveStyle(), selector_style) end
    -- 选择器悬浮样式
    local hover_selector = selector .. ":hover";
    selector_style = self.__selector_style__[hover_selector];
    if (selector_style) then commonlib.partialcopy(style:GetHoverStyle(), selector_style) end

    -- 选择器聚焦样式
    local focus_selector = selector .. ":focus";
    selector_style = self.__selector_style__[focus_selector];
    if (selector_style) then commonlib.partialcopy(style:GetFocusStyle(), selector_style) end

    -- 滚动条样式
    -- local scrollBarStyle = element:GetScrollBarStyle();
    -- selector_style = self.__selector_style__[selector .. "::scrollbar"];
    -- if (selector_style) then commonlib.partialcopy(scrollBarStyle["scrollbar"], selector_style) end
    -- selector_style = self.__selector_style__[selector .. "::scrollbar-thumb"];
    -- if (selector_style) then commonlib.partialcopy(scrollBarStyle["scrollbar-thumb"], selector_style) end
    
    -- 标记选择器
    local style_selector = style:GetSelector();
    style_selector[selector] = true;
    style_selector[active_selector] = true;
    style_selector[hover_selector] = true;
    style_selector[focus_selector] = true;
end

-- 生效类选择器样式
function StyleSheet:ApplyClassSelectorStyle(element, style)
    local classes = element:GetAttrStringValue("class",  "");
    for class in string.gmatch(classes, "%s*([^%s]+)%s*") do 
        self:ApplySelectorStyle("." .. class, style, element);
    end
end

-- 生效组合选择器样式
function StyleSheet:ApplyComboSelectorStyle(element, style)
    for selector in pairs(self.__selector_style__) do
        if (IsElementSelector(selector, element)) then
            self:ApplySelectorStyle(selector, style, element);
        end
    end
end

-- 生效标签名选择器样式
function StyleSheet:ApplyTagNameSelectorStyle(element, style)
    local tagname = string.lower(element:GetTagName() or "");
    if (tagname == "") then return end 
    self:ApplySelectorStyle(tagname, style, element);
end

-- 生效ID选择器样式
function StyleSheet:ApplyIdSelectorStyle(element, style)
    local id = element:GetAttrStringValue("id",  "");
    if (id == "") then return end 
    self:ApplySelectorStyle("#" .. id, style, element);
end

-- 应用元素样式
function StyleSheet:ApplyElementStyle(element)
    local style = element:GetStyle();
    commonlib.cleartable(style:GetSelector());

    -- 通配符*选择器
    self:ApplySelectorStyle("*", style);

    -- 便签选择器
    self:ApplyTagNameSelectorStyle(element, style);

    -- 类选择器
    self:ApplyClassSelectorStyle(element, style);

    -- ID选择器
    self:ApplyIdSelectorStyle(element, style);

    -- 选择器组合
    self:ApplyComboSelectorStyle(element, style);
end

-- -- 应用元素动画
-- function StyleSheet:ApplyElementAnimationStyle(element)
--     local style = element:GetStyle();
--     local animationName = style:GetAnimationName();
--     local function GetAnimation(sheet)
--         if (not sheet) then return nil end

--         local animation = sheet.__animation_style__[animationName];
--         if (animation) then return animation end

--         return GetAnimation(sheet.__inherit_stylesheet__);
--     end

--     element:GetAnimation():SetKeyFrames(GetAnimation(self));
-- end

return StyleSheet;